package com.gitee.easyopen.sdk;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.httpclient.HttpStatus;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.CookieStore;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.cookie.BasicClientCookie;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.util.IOUtils;
import com.gitee.easyopen.sdk.resp.ErrorResp;
import com.gitee.easyopen.sdk.util.AESUtil;
import com.gitee.easyopen.sdk.util.PostUtil;
import com.gitee.easyopen.sdk.util.RSAUtil;
import com.gitee.easyopen.sdk.util.SignUtil;

/**
 * 请求客户端，创建一个即可。
 * 
 * @author hc.tang
 *
 */
public class OpenClient {

    private static Logger logger = LoggerFactory.getLogger(OpenClient.class);
    private static final String UTF8 = "UTF-8";
    private static final String ZH_CN = "zh-CN";

    private static final String ACCEPT_LANGUAGE = "Accept-Language";
    private static final String AUTHORIZATION = "Authorization";
    private static final String PREFIX_BEARER = "Bearer ";

    private String url;
    private String appKey;
    private String secret;
    private String lang = ZH_CN;

    private String appKeyName = "app_key";
    private String signName = "sign";
    private String dataName = "data";

    /**
     * 请求模式,使用RequestMode.PUBLIC_PRIVATE_KEY请求数据以及返回结果都经过加密处理
     */
    private RequestMode requestMode = RequestMode.SIGNATURE;

    private CookieStore cookieStore;

    public OpenClient(String url, String appKey, String secret, String lang) {
        this(url, appKey, secret);
        this.lang = lang;
    }

    public OpenClient(String url, String appKey, String secret) {
        this.url = url;
        this.appKey = appKey;
        this.secret = secret;
        
        init(url);
    }

    private void init(String url) {
        try {
            logger.info("初始化客户端");
            System.out.println("初始化客户端");
            // 传递随机数
            String randomKeyEncrypted = createRandomKeyByPublicKey();
            url = url + "/handshake";
            String json = doPost(url, randomKeyEncrypted, null, null);
            checkResp(json);
            logger.info("OpenClient inited by url:{}", url);
        } catch (Exception e) {
            e.printStackTrace();
            logger.error(e.getMessage(),e);
            throw new RuntimeException("初始化失败", e);
        }
    }

    private static String createRandomKeyByPublicKey() throws Exception {
        String randomKey = SdkContext.getRandomKey();
        String publicKey = SdkContext.getPublicKey();
        return RSAUtil.encryptByPublicKey(randomKey, RSAUtil.getPublicKey(publicKey));
    }

    private static void checkResp(String resp) throws Exception {
        JSONObject jsonObj = JSONObject.parseObject(resp);
        if(!"0".equals(jsonObj.getString("code"))) {
            throw new RuntimeException(jsonObj.getString("msg"));
        }
        String randomKey = SdkContext.getRandomKey();
        String publicKey = SdkContext.getPublicKey();

        String data = jsonObj.getString("data");
        String desStr = RSAUtil.decryptByPublicKey(data, RSAUtil.getPublicKey(publicKey));

        String content = AESUtil.decryptFromHex(desStr, randomKey);
        // 一致
        boolean same = "0".equals(content);

        if (!same) {
            throw new RuntimeException("数据传输有误");
        }
    }

    public <T> T request(BaseReq<T> request) {
        return request(request, null);
    }
    

    public <T> T requestWithJwt(BaseReq<T> request, String jwt) {
        return request(request, jwt);
    }

    private <T> T request(BaseReq<T> request, String jwt) {
        String json = JSON.toJSONString(request);
        JSONObject jsonObj = JSONObject.parseObject(json);
        String data = jsonObj.getString(dataName);
        if (data == null || "".equals(data)) {
            data = "{}";
        }
        try {
            data = URLEncoder.encode(data, UTF8);
            jsonObj.put(dataName, data);
        } catch (UnsupportedEncodingException e) {
        }
        jsonObj.put(appKeyName, this.appKey);
        logger.debug("请求参数：{}", json);
        String body = this.post(url, jsonObj, jwt);
        T t = (T) JSON.parseObject(body, request.buildRespClass());
        return t;
    }
    

    /**
     * 发送请求
     * 
     * @param url
     *            请求连接
     * @param params
     *            请求参数
     * @return 服务端返回的内容
     */
    private String post(String url, JSONObject params, String jwt) {
        Object finalParam = null;
        Map<String, String> header = buildHeader();

        if (jwt != null) {
            header.put(AUTHORIZATION, PREFIX_BEARER + jwt);
        }
        
        if (this.requestMode == RequestMode.SIGNATURE) {
            finalParam = buildSignTypeParam(params);
        } else if (this.requestMode == RequestMode.ENCRYPT) {
            try {
                finalParam = this.buildRsaContent(params);
                this.encryptHeader(header);
                url = url + "/ssl";
            } catch (Exception e) {
                throw new RuntimeException("构建参数失败", e);
            }
        } else {
            throw new RuntimeException("找不到请求模式:" + requestMode.name());
        }

        return this.doPost(url, finalParam, header, this.cookieStore);
    }
    
    // 加密header
    private void encryptHeader(Map<String, String> header) throws Exception {
        String randomKey = SdkContext.getRandomKey();
        
        Set<Entry<String, String>> entrySet = header.entrySet();
        for (Entry<String, String> entry : entrySet) {
            String key = entry.getKey();
            String value = entry.getValue();
            
            value = doAes(value, randomKey);
            
            header.put(key, value);
        }
    }

    private Map<String, String> buildHeader() {
        Map<String, String> header = new HashMap<String, String>();
        header.put(ACCEPT_LANGUAGE, lang);
        return header;
    }

    private String buildRsaContent(JSONObject params) throws Exception {
        String randomKey = SdkContext.getRandomKey();
        return doAes(params.toJSONString(), randomKey);
    }
    
    protected String doAes(String content,String key) throws Exception {
        return AESUtil.encryptToHex(content, key);
    }

    private JSONObject buildSignTypeParam(JSONObject params) {
        String sign = null;
        try {
            sign = SignUtil.buildSign(params, this.secret);
        } catch (IOException e) {
            logger.error(e.getMessage(), e);
            throw new SdkException("签名构建失败");
        }

        params.put(signName, sign);

        return params;
    }

    private String doPost(String url, Object params, Map<String, String> header, CookieStore cookieStore) {
        CloseableHttpResponse response = null;
        try {
            response = PostUtil.post(url, params, header, cookieStore);

            int state = response.getStatusLine().getStatusCode();
            if (state != HttpStatus.SC_OK) {
                throw new Exception("HttpStatus is " + state);
            }

            this.setCookie(url, response);

            String body = EntityUtils.toString(response.getEntity());
            // 对结果进行解密
            if(this.requestMode == RequestMode.ENCRYPT) {
                body = AESUtil.decryptFromHex(body, SdkContext.getRandomKey());
            }
            return body;
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            ErrorResp result = new ErrorResp();
            result.setCode("-1");
            result.setMsg(e.getMessage());
            return JSON.toJSONString(result);
        }finally {
            IOUtils.close(response);
        }
    }

    public String getLang() {
        return lang;
    }

    public void setLang(String lang) {
        this.lang = lang;
    }

    public RequestMode getRequestMode() {
        return requestMode;
    }

    public void setRequestMode(RequestMode requestMode) {
        this.requestMode = requestMode;
    }

    private void setCookie(String url, HttpResponse response) {
        if(this.cookieStore == null) {
            String domain = buildDomain(url);
            this.cookieStore = buildCookieStore(domain, response); // 保存cookie
        }
    }
    
    private static String buildDomain(String url) {
        int start = url.indexOf("//");
        url = url.substring(start + 2);
        int end = url.indexOf(":");
        if (end > -1) {
            return url.substring(0, end);
        } else {
            end = url.indexOf("/");
            return url.substring(0, end);
        }
    }

    private static CookieStore buildCookieStore(String domain, HttpResponse response) {

        CookieStore cookieStore = new BasicCookieStore();
        Header header = response.getFirstHeader("Set-Cookie");
        if (header == null) {
            return null;
        }
        // JSESSIONID
        String setCookie = header.getValue();
        String JSESSIONID = setCookie.substring("JSESSIONID=".length(), setCookie.indexOf(";"));
        // 新建一个Cookie
        BasicClientCookie cookie = new BasicClientCookie("JSESSIONID", JSESSIONID);
        cookie.setVersion(0);
        cookie.setDomain(domain);
        cookie.setPath("/");
        cookie.setSecure(false);

        cookieStore.addCookie(cookie);

        return cookieStore;
    }

}
